can i use inputs and outputs
I know the inputs, but I do not know the outputs until I'm in the logic of my custom Task. Is there anyway to still use inputs and outputs in my .targets file to control when things will build, or do I need to implement this myself in my Task class?
Thanks,
Mike
Answers
Inputs and Outputs must be declared in the project file. Their values may be computed by combining existing properties and/or items, or by previously built targets, but they must be specified in the project file.
Are the names of your Outputs not simply transformations of the names of the Inputs? e.g., something like inputs: "a.txt;b.txt" and "a.data;b.data". If they are, you may use a transform like so (continuing this example):
Inputs="@(TextFiles)" Outputs="@(TextFiles->'%(RelativeDir)%(Filename).data')"
If you cannot describe the Outputs as a known function of the Inputs, then it's probably best to have your task do the Up-To-Date checking.
Thanks,
jeff.In your case, I'd recommend moving the up-to-date logic into the custom task. We're brainstorming ideas for making the project file's TLDA (Top Level Dependency Analysis) extensible in the future, but in the meantime you can perform this analysis in the task.
There isn't a particular pattern I could recommend, largely because the scenarios requiring custom analysis are quite diverse. But if you could describe in more detail your particular scenario, I'd be happy to advise you in creating a solution.Thanks,
jeff.Hi Mike,
Is it possible for you to separate the logic of determining the path/name of the output file from the actual generation of the file? Even if you have to have separate custom tasks for each of these operations, it may make it easier to accomplish your scenario. If you can do this (and it's not too expensive to separate these), you could factor your build like so:
<Target Name="ComputeNameOfOutput">
<ComputeNameOfOutput Inputs="@(MyInputs)">
<Output TaskParameter="Output" ItemName="MyOutput" />
</ComputeNameOfOutput>
</Target>
<Target Name="GenerateOutput"
DependsOnTargets="ComputeNameOfOutput"
Inputs="@(MyInputs)"
Outputs="@(MyOutput)">
<GenerateOutput Inputs="@(MyInputs)" OutputName="@(MyOutput)" />
</Target>
If you can reasonably factor this part of your build process so that the computation of the name and the generation of the output are separate, then you can still take advantage of MSBuild's TLDA without having to inspect the modified times.Otherwise, if you pass an item list of inputs to a task as an ITaskItem, each of these objects will have a method on them called GetMetadata which takes the name of the metadata you'd like to get (in your case, ModifiedTime). More information on this method can be found here http://msdn2.microsoft.com/en-us/library/microsoft.build.framework.itaskitem.getmetadata.aspx. Finally, once you had the name of the output, you could create a new TaskItem (information here http://msdn2.microsoft.com/en-us/library/microsoft.build.utilities.taskitem(VS.80).aspx) and it will implement the ITaskItem interface and you can in turn access the ModifiedTime metadata in the same way on it.
Hopefully one of these approaches will work for you. As always, let us know if you get stuck!
Thanks,
jeff.
All Replies
Inputs and Outputs must be declared in the project file. Their values may be computed by combining existing properties and/or items, or by previously built targets, but they must be specified in the project file.
Are the names of your Outputs not simply transformations of the names of the Inputs? e.g., something like inputs: "a.txt;b.txt" and "a.data;b.data". If they are, you may use a transform like so (continuing this example):
Inputs="@(TextFiles)" Outputs="@(TextFiles->'%(RelativeDir)%(Filename).data')"
If you cannot describe the Outputs as a known function of the Inputs, then it's probably best to have your task do the Up-To-Date checking.
Thanks,
jeff.There is a 1-1 ration of inputs to outputs, but the names are not always a transformation of the input name. The other issue is that the output name is not known until I'm in the logic of the custom task. I tried to specify an output parameter to write back out the output file names, but msbuild put out a message stating that it was skipping my target because it has no outputs. Is there a way to implement this in the build file, or am I going to need to check the dates in my task? If I need to check them in my task, is there any documentation that shows the recommended way of doing this?
Thanks,
Mike
In your case, I'd recommend moving the up-to-date logic into the custom task. We're brainstorming ideas for making the project file's TLDA (Top Level Dependency Analysis) extensible in the future, but in the meantime you can perform this analysis in the task.
There isn't a particular pattern I could recommend, largely because the scenarios requiring custom analysis are quite diverse. But if you could describe in more detail your particular scenario, I'd be happy to advise you in creating a solution.Thanks,
jeff.I won't need to check any modification times on the output files, just on the inputs. If the file has been modified, than it needs to be rebuilt. Is there any built-in metadata to check this, or should I just use standard .net framework classes to check the mod times on the files?
Thanks,
Mike
Hi Mike,
I don't understand how you will determine if the file has been modified if you don't have an output to compare it with. For example, if I wanted to determine if any of the c# source files (inputs) compiled into an assembly (outputs) had been modified, I'd check to see if any of them were "newer" or modified since the last time the assembly was modified. So I would in fact look at the modified times of both the inputs and the outputs.
In your case, no such comparison would exist. Are you trying to determine if any of the build files have been modified since the time of the last build? If so, you may be able to come up with a solution where you cached the file modification times during each build, and during a subsequent build compared those from the cache, but this seems like it could be problematic (what happens if, say, a new source file with an older modification time is included in the build? or if one of the files previously built was excluded this time? you could reasonably answer these questions, but it quickly grows pretty complex.).If you're interested in simply accessing modification times on build items, there is a piece of well-known metadata on each item (both in tasks and in the project file) populated by MSBuild, called "ModifiedTime". You can find more info about this and other metadata populated by MSBuild here http://msdn2.microsoft.com/en-us/library/ms164313.aspx.
I feel like I may not have adequately helped out with your scenario but I'd still like to help in any way I can. I think if I understood more about how you will decide if the target is up-to-date (and perhaps what the inputs and output(s) are, like txt files and a resource file, etc), I may be able to offer better help.
Thanks,
jeff.Hi Jeff,
You're correct, that I do need to check the mod times on the outputs as well. Basically we have a code generator that takes an input file and produces code in another language as the output file. There are some external forces that determine the name and location of the output file. For this reason, it's name is not know until the logic in my custom task is executed. How can I use this modifiedtime metadata? Do I need to pass it's value in to my task as a parameter? Can it be used for the output files as well, even though there value is not known when the build is called?
Thanks,
Mike
Hi Mike,
Is it possible for you to separate the logic of determining the path/name of the output file from the actual generation of the file? Even if you have to have separate custom tasks for each of these operations, it may make it easier to accomplish your scenario. If you can do this (and it's not too expensive to separate these), you could factor your build like so:
<Target Name="ComputeNameOfOutput">
<ComputeNameOfOutput Inputs="@(MyInputs)">
<Output TaskParameter="Output" ItemName="MyOutput" />
</ComputeNameOfOutput>
</Target>
<Target Name="GenerateOutput"
DependsOnTargets="ComputeNameOfOutput"
Inputs="@(MyInputs)"
Outputs="@(MyOutput)">
<GenerateOutput Inputs="@(MyInputs)" OutputName="@(MyOutput)" />
</Target>
If you can reasonably factor this part of your build process so that the computation of the name and the generation of the output are separate, then you can still take advantage of MSBuild's TLDA without having to inspect the modified times.Otherwise, if you pass an item list of inputs to a task as an ITaskItem, each of these objects will have a method on them called GetMetadata which takes the name of the metadata you'd like to get (in your case, ModifiedTime). More information on this method can be found here http://msdn2.microsoft.com/en-us/library/microsoft.build.framework.itaskitem.getmetadata.aspx. Finally, once you had the name of the output, you could create a new TaskItem (information here http://msdn2.microsoft.com/en-us/library/microsoft.build.utilities.taskitem(VS.80).aspx) and it will implement the ITaskItem interface and you can in turn access the ModifiedTime metadata in the same way on it.
Hopefully one of these approaches will work for you. As always, let us know if you get stuck!
Thanks,
jeff.Thanks Jeff. I took the above approach and created a separate task to determine the outputs and it's working fine. If none have changed than it skips the build. My question is how to generate only files that have changed? If I modify only 1 file, all of them are being generated. Also, I noticed that when I build from the command line I get a nice message telling me the target was skipped, but from within VS, it tells me 1 build succeeded, 0 skipped. Is there a way to tell it that some were skipped?
Thanks,
Mike
Hi Mike,
I'll try to separate the two questions. First:
"My question is how to generate only files that have changed? If I modify only 1 file, all of them are being generated."I'm not sure I understand what you're trying to do here, but I'll take a guess and if I'm off base please correct me. It sounds like for each input and output combination, you only want to generate that particular output if its corresponding input has changed. MSBuild actually makes this pretty easy to do (it can do it for you), but you'll probably want to refactor the code I last posted. Here's what I posted before:
<Target Name="ComputeNameOfOutput">
<ComputeNameOfOutput Inputs="@(MyInputs)">
<Output TaskParameter="Output" ItemName="MyOutput" />
</ComputeNameOfOutput>
</Target>
<Target Name="GenerateOutput"
DependsOnTargets="ComputeNameOfOutput"
Inputs="@(MyInputs)"
Outputs="@(MyOutput)">
<GenerateOutput Inputs="@(MyInputs)" OutputName="@(MyOutput)" />
</Target>As you can see, we generate a separate item list with the names of the outputs, then specify the inputs and outputs with these two item lists. However, this approach doesn't make it clear to MSBuild that there is an actual 1:1 correspondence between the items in MyInputs and those in MyOutputs. In order for MSBuild to recognize this correspondence, and enable a "partial" build of the GenerateOutput target, we should instead use a single item list and express the Outputs as a transform of the Inputs.
So to do that, I would modify the code above like so:
<Target Name="AssignOutputName">
<AssignOutputName Inputs="@(MyInputs)">
<Output TaskParameter="Output" ItemName="MyInputsWithOutputName" />
</AssignOutputName>
</Target>
<Target Name="GenerateOutput"
DependsOnTargets="AssignOutputName"
Inputs="@(MyInputsWithOutputName)"
Outputs="@(MyInputsWithOutputName->'%(OutputName)')">
<GenerateOutput Inputs="@(MyInputsWithOutputName)" />
</Target>So I've replaced the "ComputeNameOfOutput" task with another task "AssignOutputName" which does almost exactly the same thing, but with one key difference. Instead of outputting a list of the names of the outputs, it outputs a list with the same items passed in but with a piece of metadata called "OutputName" attached to each item in the list. So a sketch of the code in the task's Execute() method might look something like this:
TaskItem[] outputs = new TaskItem[inputs.Length];
for(int i = 0; i < inputs.Length; i++)
{
outputs[ i ] = new TaskItem(inputs[ i ]);
string outputName = ComputeNameOfOutput(inputs[ i ]); //this function is what does the real work
outputs[ i ].SetMetadata("OutputName", outputName); //this method appends the metadata
}
return true;So the items the task passes out are basically the same as those passed in, but with the additional piece of useful metadata appended to them. I think that's the direction you're trying to go, and hopefully this all made sense. There's more information on "partial" target builds here and more information about using item metadata and transforms here.
Now your second question:
"Also, I noticed that when I build from the command line I get a nice message telling me the target was skipped, but from within VS, it tells me 1 build succeeded, 0 skipped. Is there a way to tell it that some were skipped?"
The message you're seeing in Visual Studio is actually a summary of the results of each project's build, not the targets within the project. If you'd like to see more details in the Visual Studio output window (including the messages logged when targets are skipped), you'll need to increase the logging verbosity. To do this, go to "Tools->Options..." and select the "Build and Run" node underneath "Projects and Solutions". You should see a dropdown for the "MSBuild project build output verbosity"; if you change this to detailed or diagnostic, you should begin seeing the target skipped messages in the output window (though you'll also of course see many more other messages).
Please let me know if I can offer more help.
Thanks,
jeff.Thanks, that works. In regards to the second question. I changed the level of verbosity, but did not see a difference in the messages that came out. I believe this is because I have a custom logger that is capturing build messages and only outputting them to the output window if they meet my conditions. Is there any way to tell these event handlers to do the default implementation with the message if it's not one I want to customize?
Thanks,
Mike
Hi Mike,
Increasing the verbosity in the IDE should work, even if you have a custom logger attached. The logging infrastructure in MSBuild was designed so that loggers cannot interfere with each other. Obviously there are exceptions to this rule - such as catastrophic failures (like unhandled exceptions) or shared resources (like the console or a database), but for the most part loggers should operate independently of one another.So even if you've registered a custom logger in Visual Studio, I'd still expect Visual Studio's HostLogger to operate as normal. If this isn't the case, I'd be curious to learn more about your logger and how you attached it to run within Visual Studio, what it does, what output you do see in the output window on diagnostic verbosity, etc.
Thanks,
jeff.We have a VS Package that we are developing for our language. The class we inherit from for our project system is ProjectNode. This class has a property called BuildLogger. This is what we set assign our custom Logger to. Our logger is using the following event handler's: BuildWarningEventHandler, BuildErrorEventHandler, BuildMessageEventHandler. Now from our custom task we are simply logging messages either using the built-in Log object or by creating buildeventargs and logging them with the built-in BuildEngine object. Our logger captures these and than does customization depending on the message. Now, if I set a breakpoint in my BuildMessageEventHandler I see lots of messages going through, but I am only doing something with them if the sender is my task. I guess I'm not sure what happens to those messages that I don't do anything with. This is what I get in the Build pane of the Output window each time:
------ Build started: Project: XgenClassLibrary1, Configuration: Release Any CPU ------
========== Build: 1 succeeded or up-to-date, 0 failed, 0 skipped ==========It doesn't matter if I change the verbosity level, I still get just these messages. I also tried setting the verbosity on my Logger object, but it didn't change anything.
Thanks,
Mike

