La soluzione è creare un Target che aggiunge i file al Compile ItemGroup anziché aggiungerli esplicitamente nel file .csproj. In questo modo Intellisense li vedrà e saranno compilati nel tuo eseguibile, ma non verranno visualizzati in Visual Studio.
È inoltre necessario assicurarsi che il vostro obiettivo è aggiunto alla proprietà CoreCompileDependsOn
in modo che eseguirà prima dell'esecuzione del compilatore.
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
...
</Project>
e l'importazione nel tuo .csproj con <Import Project="MyTool.targets">
. Un file .targets è consigliato anche per casi singoli perché separa il tuo codice personalizzato da quello in .csproj gestito da Visual Studio.
Costruire il nome del file generato (s)
Se si sta creando uno strumento generalizzato e/o utilizzando un file .targets separata, probabilmente non si desidera elencare esplicitamente ogni file nascosto. Invece si desidera generare i nomi di file nascosti da altre impostazioni nel progetto.Per esempio, se si desidera che tutti i file di risorse di avere corrispondenti file di utensili generati nella directory "obj", il vostro obiettivo sarebbe:
<Target Name="AddToolOutput">
<ItemGroup>
<Compile Include="@(Resource->'$(IntermediateOutputPath)%(FileName)%(Extension).g.cs')" />
</ItemGroup>
</Target>
La proprietà "IntermediateOutputPath" è ciò che tutti sappiamo come directory "obj" , ma se l'utente finale dei tuoi .target lo ha personalizzato, i tuoi file intermedi verranno trovati nello stesso posto. Se preferisci che i tuoi file generati siano nella directory principale del progetto e non nella directory "obj", puoi lasciarlo spento.
Se si desidera che lo del dei file di un tipo di articolo esistente venga elaborato dal proprio strumento personalizzato? Ad esempio, potresti voler generare file per tutti i file di pagina e di risorse con estensione ".xyz".
<Target Name="AddToolOutput">
<ItemGroup>
<MyToolFiles Include="@(Page);@(Resource)" Condition="'%(Extension)'=='.xyz' />
<Compile Include="@(MyToolFiles->'$(IntermediateOutputPath)%(FileName)%(Extension).g.cs')"/>
</ItemGroup>
</Target>
Si noti che non è possibile utilizzare la sintassi dei metadati come% (Extension) in un'ItemGroup di livello superiore, ma si può farlo entro un bersaglio.
Utilizzo di un tipo di elemento personalizzato (aka costruire azione)
I file processi di cui sopra che hanno un tipo di elemento esistente, ad esempio pagine, risorse, o compilare (Visual Studio chiama questo il "Build Azione"). Se i tuoi articoli sono un nuovo tipo di file puoi usare il tuo tipo di oggetto personalizzato. Per esempio, se i file di input sono chiamati file "XYZ", il file di progetto possono definire "XYZ" come un tipo di elemento valido:
<ItemGroup>
<AvailableItemName Include="Xyz" />
</ItemGroup>
dopo di che Visual Studio vi permetterà di selezionare "XYZ" in azione Costruire nelle proprietà del file, con conseguente questo viene aggiunto al tuo .csproj:
<ItemGroup>
<Xyz Include="Something.xyz" />
</ItemGroup>
Ora è possibile utilizzare il "XYZ" tipo di elemento per creare i nomi dei file per l'uscita strumento, proprio come abbiamo fatto in precedenza con la "risorsa" tipo di articolo:
<Target Name="AddToolOutput">
<ItemGroup>
<Compile Include="@(Xyz->'$(IntermediateOutputPath)%(FileName)%(Extension).g.cs')" />
</ItemGroup>
</Target>
Quando si utilizza un tipo di oggetto personalizzato, è possibile far sì che anche i propri elementi vengano gestiti mediante meccanismi incorporati mappandoli su un altro tipo di oggetto (noto anche come Azione build). Ciò è utile se i file "Xyz" sono in realtà file .cs o .xaml o se devono essere creati
EmbeddedResources. Per esempio è possibile causare tutti i file con "Build Azione" di Xyz a anche essere compilato:
<ItemGroup>
<Compile Include="@(Xyz)" />
</ItemGroup>
O se i file di origine "XYZ" devono essere conservati come risorse incorporate, è possibile esprimere in questo modo:
<ItemGroup>
<EmbeddedResource Include="@(Xyz)" />
</ItemGroup>
Si noti che il secondo esempio non funzionerà se lo si inserisce all'interno del Target, poiché il target non viene valutato fino a poco prima della compilazione del core. Per fare in modo che questo funzioni all'interno di un Target devi elencare il nome del target nella proprietà PrepareForBuildDependsOn invece di CoreCompileDependsOn.
Invocare il generatore di codice personalizzato da MSBuild
essere andati fino a creare un file .targets, si potrebbe considerare invocando il vostro strumento direttamente da MSBuild piuttosto che utilizzare un evento di pre-compilazione separata o Visual Studio di imperfetto Meccanismo "Strumento personalizzato".
Per fare questo:
- Creare un progetto Libreria di classi con un riferimento a Microsoft.Build.Framework
- Aggiungere il codice per implementare il generatore di codice personalizzato
- aggiungere una classe che implementa ITask, e nel metodo Execute chiama il tuo generatore di codice personalizzato
- Aggiungi un elemento
UsingTask
al tuo file .targets e nel tuo target aggiungi una chiamata alla tua nuova attività
Qui è tutto ciò che serve per implementare ITask:
public class GenerateCodeFromXyzFiles : ITask
{
public IBuildEngine BuildEngine { get; set; }
public ITaskHost HostObject { get; set; }
public ITaskItem[] InputFiles { get; set; }
public ITaskItem[] OutputFiles { get; set; }
public bool Execute()
{
for(int i=0; i<InputFiles.Length; i++)
File.WriteAllText(OutputFiles[i].ItemSpec,
ProcessXyzFile(
File.ReadAllText(InputFiles[i].ItemSpec)));
}
private string ProcessXyzFile(string xyzFileContents)
{
// Process file and return generated code
}
}
E qui è l'elemento UsingTask e un obiettivo che lo chiama:
<UsingTask TaskName="MyNamespace.GenerateCodeFromXyzFiles" AssemblyFile="MyTaskProject.dll" />
<Target Name="GenerateToolOutput">
<GenerateCodeFromXyzFiles
InputFiles="@(Xyz)"
OutputFiles="@(Xyz->'$(IntermediateOutputPath)%(FileName)%(Extension).g.cs')">
<Output TaskParameter="OutputFiles" ItemGroup="Compile" />
</GenerateCodeFromXyzFiles>
</Target>
noti che elemento di uscita di questo obiettivo pone la lista di uscita file direttamente in Compile, quindi non è necessario utilizzare un ItemGroup separato per farlo.
non come il vecchio meccanismo di "strumento personalizzato" è imperfetto e perché usarlo
Una nota riguardo il meccanismo di "strumento personalizzato" di Visual Studio: In .NET Framework 1.x non abbiamo avuto MSBuild, quindi abbiamo dovuto affidarci a Visual Studio per costruire i nostri progetti. Per ottenere Intellisense sul codice generato, Visual Studio disponeva di un meccanismo denominato "Strumento personalizzato" che può essere impostato nella finestra Proprietà su un file. Il meccanismo era fondamentalmente difettoso in vari modi, motivo per cui è stato sostituito con gli obiettivi di MSBuild. Alcuni dei problemi con la funzionalità "Strumento personalizzato" erano:
- Uno "Strumento personalizzato" costruisce il file generato ogni volta che il file viene modificato e salvato, non quando il progetto è compilato. Ciò significa che qualsiasi cosa che modifica il file esternamente (come un sistema di controllo di revisione) non aggiorna il file generato e spesso ottieni codice stantio nell'eseguibile.
- L'output di uno "Strumento personalizzato" doveva essere fornito con l'albero di origine a meno che il destinatario non avesse sia Visual Studio sia il tuo "Strumento personalizzato".
- Lo "Strumento personalizzato" doveva essere installato nel registro e non poteva semplicemente essere referenziato dal file di progetto.
- L'output dello "Strumento personalizzato" non è memorizzato nella directory "obj".
Se si utilizza la vecchia funzione "Strumento personalizzato", si consiglia vivamente di passare all'utilizzo di un'attività MSBuild. Funziona bene con Intellisense e ti consente di creare il tuo progetto senza nemmeno installare Visual Studio (tutto ciò che ti serve è NET Framework).
Quando verrà eseguita l'attività di creazione personalizzata?
In generale il vostro compito di generazione personalizzata verrà eseguito:
- In sottofondo quando Visual Studio apre la soluzione, se il file generato non è aggiornato
- Sullo sfondo ogni volta che si salva un i file di input in Visual Studio
- Ogni volta che si genera, se il file generato non è aggiornato
- Ogni volta che si ricostruisce
Per essere più precisi:
- Una generazione incrementale IntelliSense viene eseguito quando si avvia Studio Visive e ogni volta che un file viene salvato all'interno di Visual Studio. Questo eseguirà il generatore se il file di output manca uno dei file di input più recenti rispetto all'uscita del generatore.
- Una build incrementale regolare viene eseguita ogni volta che si utilizza qualsiasi comando "Build" o "Esegui" in Visual Studio (incluse le opzioni di menu e premendo F5) o quando si esegue "MSBuild" dalla riga di comando. Come la build incrementale IntelliSense, eseguirà anche il generatore solo se il file generato non è aggiornato
- Una generazione completa normale viene eseguita ogni volta che si utilizza uno dei comandi "Ricostruisci" in Visual Studio o quando si esegue " MSBuild/t: Ricostruisci "dalla riga di comando. Avvia sempre il tuo generatore se ci sono ingressi o uscite.
È possibile forzare l'esecuzione del generatore in altri momenti, ad esempio quando alcune variabili di ambiente cambiano o forzare l'esecuzione in modo sincrono anziché in background.
per causare il generatore di eseguire nuovamente anche quando nessun file di input sono cambiati, il modo migliore è di solito per aggiungere un ingresso aggiuntivo per il vostro obiettivo che è un file di input fittizio memorizzato nella directory "obj". Quindi, ogni volta che si modifica una variabile di ambiente o alcune impostazioni esterne che dovrebbero forzare la rigenerazione del proprio strumento generatore, è sufficiente toccare questo file (ad esempio crearlo o aggiornare la data di modifica).
Per forzare il generatore a funzionare in modo sincrono anziché attendere che IntelliSense lo esegua in background, basta usare MSBuild per creare il target specifico. Questo potrebbe essere semplice come eseguire "MSBuild/t: GenerateToolOutput", oppure VSIP può fornire un modo integrato per chiamare obiettivi di compilazione personalizzati. In alternativa puoi semplicemente invocare il comando Crea e attendere che si completi.
Nota che "File di input" in questa sezione si riferisce a qualsiasi cosa sia elencata nell'attributo "Ingressi" dell'elemento Target.
Note finali
si possono ottenere gli avvertimenti da Visual Studio che non sa se fidarsi di file lo strumento personalizzato .targets. Per risolvere questo problema, aggiungilo alla chiave di registro HKEY_LOCAL_MACHINE \ SOFTWARE \ Microsoft \ VisualStudio \ 9.0 \ MSBuild \ SafeImports.
Ecco un riepilogo di ciò che è reale.file di obiettivi sarebbe simile con tutti i pezzi a posto:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<CoreCompileDependsOn>$(CoreCompileDependsOn);GenerateToolOutput</CoreCompileDependsOn>
</PropertyGroup>
<UsingTask TaskName="MyNamespace.GenerateCodeFromXyzFiles" AssemblyFile="MyTaskProject.dll" />
<Target Name="GenerateToolOutput" Inputs="@(Xyz)" Outputs="@(Xyz->'$(IntermediateOutputPath)%(FileName)%(Extension).g.cs')">
<GenerateCodeFromXyzFiles
InputFiles="@(Xyz)"
OutputFiles="@(Xyz->'$(IntermediateOutputPath)%(FileName)%(Extension).g.cs')">
<Output TaskParameter="OutputFiles" ItemGroup="Compile" />
</GenerateCodeFromXyzFiles>
</Target>
</Project>
Per favore fatemi sapere se avete domande o c'è qualcosa qui non hai capito.
dove (relativa ai file sorgente) stai salvando i file generati? – luke
La directory di output è la stessa della directory di input. – jaws
Cosa intendi quando dici che i file code-behind di WPF sono nascosti? Se creo un'applicazione WPF, ottengo un file denominato MainWindow.xaml, che può essere espanso per mostrare quello che credo sia il file code-behind, MainWindow.xaml.cs. – ErikHeemskerk