<?xml version="1.0" encoding="utf-8"?>
<!--
/****************************************************************************
**
** Copyright (C) 2018 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt VS Tools.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
-->

<!--
///////////////////////////////////////////////////////////////////////////////////////////////////
/// Qt/MSBuild global definitions
///////////////////////////////////////////////////////////////////////////////////////////////////
// -->
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

  <!--
  /////////////////////////////////////////////////////////////////////////////////////////////////
  // Build dependencies
  // -->
  <PropertyGroup>
    <BuildDependsOn>
      QtVersion;
      $(BuildDependsOn);
      Qt
    </BuildDependsOn>
    <CleanDependsOn>
      $(CleanDependsOn);
      QtClean
    </CleanDependsOn>
    <DesignTimeBuildInitTargets>
      $(DesignTimeBuildInitTargets);
      Qt
    </DesignTimeBuildInitTargets>
    <ComputeCompileInputsTargets>
      $(ComputeCompileInputsTargets);
      Qt
    </ComputeCompileInputsTargets>
    <ComputeLinkInputsTargets>
      $(ComputeLinkInputsTargets);
      Qt
    </ComputeLinkInputsTargets>
  </PropertyGroup>

  <!--
  /////////////////////////////////////////////////////////////////////////////////////////////////
  // Qt/MSBuild global properties
  // -->
  <Import Project="..\version.targets" Condition="Exists('..\version.targets')"/>
  <PropertyGroup>
    <QtMsBuildVersion>$(QtVSToolsVersion)</QtMsBuildVersion>
    <QtDebug Condition="'$(QtDebug)' == ''">false</QtDebug>
    <QtLogFilePath Condition="'$(QtLogFilePath)' == ''"
      >$([System.IO.Path]::Combine($(ProjectDir),$(IntDir)qt_work.log))</QtLogFilePath>
    <QtMaxProcs Condition="'$(QtMaxProcs)' == ''"
      >$([System.Environment]::ProcessorCount)</QtMaxProcs>
  </PropertyGroup>

  <!--
  /////////////////////////////////////////////////////////////////////////////////////////////////
  /// TARGET QtGetDefaultClCompile
  /////////////////////////////////////////////////////////////////////////////////////////////////
  // Get default C++ properties
  // -->
  <Target Name="QtGetDefaultClCompile" BeforeTargets="FixupCLCompileOptions">
    <ItemGroup>
      <ClCompile Include="DefaultClCompile"/>
    </ItemGroup>
  </Target>

  <!--
  /////////////////////////////////////////////////////////////////////////////////////////////////
  /// TARGET QtClean
  /////////////////////////////////////////////////////////////////////////////////////////////////
  // Clean-up from previous build
  // -->
  <Target Name="QtClean">
    <Message Importance="High" Condition="'$(QtDebug)' == 'true'" Text="## Qt Clean"/>
    <Delete Files="$(QtLogFilePath)"/>
  </Target>

  <!--
  /////////////////////////////////////////////////////////////////////////////////////////////////
  /// TARGET QtVersion
  /////////////////////////////////////////////////////////////////////////////////////////////////
  // Print debug message with Qt/MSBuild version
  // -->
  <Target Name="QtVersion">
    <Message Importance="High" Condition="'$(QtDebug)' == 'true'"
      Text="Qt/MSBuild v$(QtMsBuildVersion) ($(MSBuildThisFileDirectory))"/>
  </Target>

  <!--
  /////////////////////////////////////////////////////////////////////////////////////////////////
  /// TARGET QtPrepare
  /////////////////////////////////////////////////////////////////////////////////////////////////
  // Prepare Qt build: read and parse work log file
  // -->
  <Target Name="QtPrepare"
    Condition="'$(QtSkipWork)' != 'true'"
    DependsOnTargets="$(QtDependsOn)"
    BeforeTargets="QtWorkPrepare">

    <Message Importance="High" Condition="'$(QtDebug)' == 'true'" Text="#### QtPrepare"/>

    <ReadLinesFromFile File="$(QtLogFilePath)">
      <Output TaskParameter="Lines" ItemName="QtLogData"/>
    </ReadLinesFromFile>
    <ItemGroup Condition="'@(QtLogData)' != ''">
      <QtWorkLog
        Include="@(QtLogData->'$([System.String]::Copy('%(QtLogData.Identity)').Split('|')[0])')">
        <Hash>$([System.String]::Copy('%(QtLogData.Identity)').Split('|')[1])</Hash>
      </QtWorkLog>
    </ItemGroup>
  </Target>

  <!--
  /////////////////////////////////////////////////////////////////////////////////////////////////
  /// TARGET QtWorkPrepare
  /////////////////////////////////////////////////////////////////////////////////////////////////
  // Analyze work request and decide if the Qt tool needs to be called or if the output from the
  // previous call is still valid.
  // -->
  <Target Name="QtWorkPrepare" DependsOnTargets="$(QtDependsOn);$(QtBuildTargets)"
    Condition="'$(QtSkipWork)' != 'true'"
    Inputs="%(QtWork.WorkType)(%(QtWork.Identity))"
    Outputs="@(QtWork->'####### Don't skip this target #######')">

    <Message Importance="High" Condition="'$(QtDebug)' == 'true' AND '@(QtWork)' != ''"
      Text="## QtWorkPrepare %(QtWork.Identity)" />

    <!--
    ///////////////////////////////////////////////////////////////////////////////////////////////
    // Calculate hash for the requested work item, based on its associated tool and options
    // -->
    <GetItemHash Condition="'@(QtWork)' != ''"
      Item="@(QtWork)" Keys="Identity;WorkType;ToolPath;Options">
      <Output TaskParameter="Hash" PropertyName="work_hash" />
    </GetItemHash>

    <!--
    ///////////////////////////////////////////////////////////////////////////////////////////////
    // Try to find entry in Qt work log for the requested work item; get logged hash
    // -->
    <PropertyGroup Condition="'@(QtWork)' != ''">
      <work_key>@(QtWork->'%(WorkType)(%(Identity))')</work_key>
      <dependencies_changed>@(QtWork->'%(DependenciesChanged)')</dependencies_changed>
      <input_changed>@(QtWork->'%(InputChanged)')</input_changed>
      <project_changed
        Condition="'$(dependencies_changed)' == 'true' AND '$(input_changed)' != 'true'"
        >true</project_changed>
    </PropertyGroup>

    <FindInList Condition="'@(QtWork)' != '' AND '$(input_changed)' != 'true'"
      CaseSensitive="false" List="@(QtWorkLog)" ItemSpecToFind="$(work_key)">
      <Output TaskParameter="ItemFound" ItemName="log_entry"/>
    </FindInList>

    <PropertyGroup Condition="'@(QtWork)' != ''">
      <log_hash Condition="'@(log_entry)' != ''">@(log_entry->'%(Hash)')</log_hash>
    </PropertyGroup>

    <!--
    ///////////////////////////////////////////////////////////////////////////////////////////////
    // Skip work item if:
    //  * work is not needed:
    //      - input was not modified
    //      - AND project was not modified OR command line did not change (i.e. hashes are the same)
    //  * OR we're in a design-time build
    // -->
    <PropertyGroup>
      <do_work
        Condition="'$(input_changed)' == 'true'
               OR ('$(project_changed)' == 'true' AND '$(log_hash)' != '$(work_hash)')"
        >true</do_work>
      <skip_work
        Condition="'$(do_work)' != 'true' OR '$(DesignTimeBuild)' == 'true'"
        >true</skip_work>
    </PropertyGroup>

    <!--
    ///////////////////////////////////////////////////////////////////////////////////////////////
    // Skip work item
    // -->
    <ItemGroup Condition="'@(QtWork)' != '' AND '$(skip_work)' == 'true'">
      <QtWorkResult Include="@(QtWork)">
        <ExitCode>0</ExitCode>
        <Skipped>true</Skipped>
      </QtWorkResult>
      <QtWork Remove="@(QtWork)" />
    </ItemGroup>

    <!--
    ///////////////////////////////////////////////////////////////////////////////////////////////
    // Generate new work log entry and ensure path to output exists
    // -->
    <ItemGroup Condition="'@(QtWork)' != '' AND '$(skip_work)' != 'true'">
      <QtWorkLog Remove="$(work_key)"/>
      <QtWorkLog Include="$(work_key)">
        <Hash>$(work_hash)</Hash>
      </QtWorkLog>
    </ItemGroup>

    <MakeDir Condition="'@(QtWork)' != '' AND '$(skip_work)' != 'true'"
      Directories="$([System.IO.Path]::GetDirectoryName(%(QtWork.OutputFile)))"/>

    <!--
    ///////////////////////////////////////////////////////////////////////////////////////////////
    // Clean-up
    // -->
    <PropertyGroup>
      <work_key/>
      <log_hash/>
      <dependencies_changed/>
      <input_changed/>
      <project_changed/>
      <do_work/>
      <skip_work/>
    </PropertyGroup>
    <ItemGroup>
      <log_entry Remove="@(log_entry)"/>
    </ItemGroup>
  </Target>

  <!--
  /////////////////////////////////////////////////////////////////////////////////////////////////
  /// TARGET QtWork
  /////////////////////////////////////////////////////////////////////////////////////////////////
  // Run Qt tools and add dynamic C++ sources to build
  // -->
  <Target Name="QtWork"
    Condition="'$(QtSkipWork)' != 'true'"
    DependsOnTargets="QtWorkPrepare;QtGetDefaultClCompile">

    <Message Importance="High" Condition="'$(QtDebug)' == 'true'"
      Text="## Qt Build $(QtBuildTargets.Replace(';',' ').Trim())" />

    <!--
    ///////////////////////////////////////////////////////////////////////////////////////////////
    // Run work in parallel processes
    // -->
    <QtRunWork
      Condition="'@(QtWork)' != ''
        AND '%(QtWork.ParallelBuild)' == 'true'
        AND '$(DesignTimeBuild)' != 'true'"
      QtWork="@(QtWork)" QtMaxProcs="$(QtMaxProcs)" QtDebug="$(QtDebug)">
      <Output TaskParameter="Result" ItemName="QtWorkResult" />
    </QtRunWork>

    <!--
    ///////////////////////////////////////////////////////////////////////////////////////////////
    // Run work in a single process
    // -->
    <QtRunWork
      Condition="'@(QtWork)' != ''
        AND '%(QtWork.ParallelBuild)' != 'true'
        AND '$(DesignTimeBuild)' != 'true'"
      QtWork="@(QtWork)" QtMaxProcs="1" QtDebug="$(QtDebug)">
      <Output TaskParameter="Result" ItemName="QtWorkResult" />
    </QtRunWork>

    <!--
    ///////////////////////////////////////////////////////////////////////////////////////////////
    // Save tracking log of files read during build; used by VS to check the up-to-date status
    // -->
    <ItemGroup Condition="'$(DesignTimeBuild)' != 'true'">
      <read_log Include="^%(QtWorkResult.FullPath);%(QtWorkResult.AdditionalDependencies)"
        Condition="'%(QtWorkResult.ExitCode)' == '0' AND '%(QtWorkResult.DisableLog)' != 'true'">
        <WorkType>%(QtWorkResult.WorkType)</WorkType>
      </read_log>
      <read_log>
        <Path Condition="$([System.String]::Copy('%(Identity)').StartsWith('^'))">%(Identity)</Path>
        <Path Condition="!$([System.String]::Copy('%(Identity)').StartsWith('^'))"
          >$([System.IO.Path]::Combine('$(MSBuildProjectDirectory)','%(Identity)'))</Path>
      </read_log>
    </ItemGroup>

    <WriteLinesToFile
      Condition="'@(read_log)' != ''"
      File="$(TLogLocation)%(read_log.WorkType).read.1u.tlog"
      Lines="@(read_log->MetaData('Path')->ToUpperInvariant());"
      Overwrite="true"
      Encoding="Unicode"/>

    <!--
    ///////////////////////////////////////////////////////////////////////////////////////////////
    // Save tracking log of files written during build; used by VS to check the up-to-date status
    // -->
    <ItemGroup Condition="'$(DesignTimeBuild)' != 'true'">
      <write_log Include="^%(QtWorkResult.FullPath);%(QtWorkResult.OutputFile)"
        Condition="'%(QtWorkResult.ExitCode)' == '0' AND '%(QtWorkResult.DisableLog)' != 'true'">
        <WorkType>%(QtWorkResult.WorkType)</WorkType>
      </write_log>
      <write_log>
        <Path Condition="$([System.String]::Copy('%(Identity)').StartsWith('^'))">%(Identity)</Path>
        <Path Condition="!$([System.String]::Copy('%(Identity)').StartsWith('^'))"
          >$([System.IO.Path]::Combine('$(MSBuildProjectDirectory)','%(Identity)'))</Path>
      </write_log>
    </ItemGroup>

    <WriteLinesToFile Condition="'@(write_log)' != ''"
     File="$(TLogLocation)%(write_log.WorkType).write.1u.tlog"
     Lines="@(write_log->MetaData('Path')->ToUpperInvariant());"
     Overwrite="true" Encoding="Unicode"/>

    <!--
    ///////////////////////////////////////////////////////////////////////////////////////////////
    // Log output files; this is used by VS to determine what files to delete on "Clean"
    // -->
    <ItemGroup Condition="'$(DesignTimeBuild)' != 'true'">
      <clean_log Include="%(QtWorkResult.OutputFile)"
        Condition="'%(QtWorkResult.ExitCode)' == '0'">
        <Source>@(QtWorkResult, '|')</Source>
      </clean_log>
    </ItemGroup>

    <WriteLinesToFile Condition="'@(clean_log)' != ''"
      File="$(TLogLocation)$(ProjectName).write.1u.tlog"
      Lines="^%(clean_log.Source);@(clean_log->'%(Fullpath)')"
      Encoding="Unicode"/>

    <!--
    ///////////////////////////////////////////////////////////////////////////////////////////////
    // Log calls to Qt tools; used in QtWorkPrepare to detect changes to the options of Qt tools
    // -->
    <WriteLinesToFile Condition="'@(QtWorkLog)' != '' AND '$(DesignTimeBuild)' != 'true'"
      File="$(QtLogFilePath)"
      Lines="@(QtWorkLog->'%(Identity)|%(Hash)')"
      Overwrite="true" Encoding="Unicode"/>

    <!--
    ///////////////////////////////////////////////////////////////////////////////////////////////
    // Generate build error if a Qt tool did not terminate correctly
    // -->
    <Error
      Condition="'%(QtWorkResult.ExitCode)' != ''
        AND '%(QtWorkResult.ExitCode)' != '0'
        AND '$(DesignTimeBuild)' != 'true'"
      File="%(QtWorkResult.Identity)" Code="%(QtWorkResult.ExitCode)"
      Text="%(QtWorkResult.WorkType) (%(QtWorkResult.ToolPath))"/>

    <!--
    ///////////////////////////////////////////////////////////////////////////////////////////////
    // Add dynamic C++ sources to build
    // -->
    <ItemGroup>
      <QtWork_ClCompile
        Condition="'%(QtWorkResult.ExitCode)' == '0' AND '%(QtWorkResult.ClCompile)' != ''"
        Include="@(QtWorkResult->'%(ClCompile)')"/>
      <QtWork_ClCompile
        Condition="Exists('$(QtVarsOutputDir)\qtvars_plugin_import.cpp')"
        Include="$(QtVarsOutputDir)\qtvars_plugin_import.cpp"/>
    </ItemGroup>

    <!-- // Copy default C++ compiler properties -->
    <Expand Condition="'@(QtWork_ClCompile)' != ''"
      Items="@(QtWork_ClCompile)"
      BaseItem="@(ClCompile->WithMetadataValue('Identity', 'DefaultClCompile'))">
      <Output TaskParameter="Result" ItemName="QtWork_ClCompile_Expanded"/>
    </Expand>

    <!-- // Force pre-compiled header include -->
    <ItemGroup>
      <QtWork_ClCompile_Expanded>
        <AdditionalIncludeDirectories
          >$(ProjectDir);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
        <ForcedIncludeFiles Condition="'%(PrecompiledHeader)' == 'Use'"
          >%(PrecompiledHeaderFile)</ForcedIncludeFiles>
      </QtWork_ClCompile_Expanded>
    </ItemGroup>

    <!-- // Add C++ source items and clean-up temp items -->
    <ItemGroup>
      <ClCompile Include="@(QtWork_ClCompile_Expanded)"/>
      <QtWork_ClCompile_Expanded Remove="@(QtWork_ClCompile_Expanded)"/>
      <QtWork_ClCompile Remove="@(QtWork_ClCompile)"/>
    </ItemGroup>

    <!--// If sources were manually selected (e.g. by the 'Compile' option in the context menu for
        // project items), add generated C++ sources to the list of selected files -->
    <PropertyGroup Condition="'$(SelectedFiles)' != ''">
      <SelectedClCompile>@(QtWorkResult->'%(ClCompile)')</SelectedClCompile>
    </PropertyGroup>
    <PropertyGroup Condition="'$(SelectedClCompile)' != ''">
      <SelectedFiles>$(SelectedFiles);$(SelectedClCompile)</SelectedFiles>
    </PropertyGroup>
    <ItemGroup Condition="'$(SelectedClCompile)' != ''">
      <SelectedFiles Include="$(SelectedClCompile)"/>
    </ItemGroup>

    <!--
    ///////////////////////////////////////////////////////////////////////////////////////////////
    // Update C++ sources with generated information
    // -->
    <PropertyGroup>
      <QtIncludePath>@(QtIncludePath->Distinct())</QtIncludePath>
    </PropertyGroup>
    <ItemGroup>
      <ClCompile_Updated Include="@(ClCompile)">
        <AdditionalIncludeDirectories
>$(QtIncludePath);%(ClCompile.AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
      </ClCompile_Updated>
      <ClCompile Remove="@(ClCompile)"/>
      <ClCompile Include="@(ClCompile_Updated)"/>
      <ClCompile_Updated Remove="@(ClCompile_Updated)"/>
    </ItemGroup>

    <!--
    ///////////////////////////////////////////////////////////////////////////////////////////////
    // Clean-up
    // -->
    <ItemGroup>
      <QtWork       Remove="@(QtWork)"/>
      <QtWorkResult Remove="@(QtWorkResult)"/>
      <QtWorkLog    Remove="@(QtWorkLog)"/>
      <read_log     Remove="@(read_log)"/>
      <write_log    Remove="@(write_log)"/>
      <clean_log    Remove="@(clean_log)"/>
    </ItemGroup>
  </Target>

  <!--
  /////////////////////////////////////////////////////////////////////////////////////////////////
  /// TARGET Qt
  /////////////////////////////////////////////////////////////////////////////////////////////////
  // Root Qt target
  // -->
  <Target Name="Qt" DependsOnTargets="QtPrepare;QtWork">
    <ItemGroup>
      <ClCompile Remove="DefaultClCompile" />
    </ItemGroup>
  </Target>

  <!--
  /////////////////////////////////////////////////////////////////////////////////////////////////
  /// TARGET QtOuterBuild
  /////////////////////////////////////////////////////////////////////////////////////////////////
  // Run targets in $(QtOuterBuildDependsOn) and then recursively invoke build
  // -->
  <Target Name="QtOuterBuild" DependsOnTargets="$(QtOuterBuildDependsOn)">
    <!--// Invoke inner build: recursive build in second MSBuild instance -->
    <MSBuild
      Projects="$(MSBuildProjectFullPath)"
      Targets="Build"
      Properties="QtInnerBuild=$(MSBuildProjectFullPath);RandomFileName=$(RandomFileName)">
    </MSBuild>
  </Target>

  <PropertyGroup
    Condition="'$(QtInnerBuild)' == '' AND '$(DesignTimeBuild)' != 'true'">
    <!--// Outer build: invoke inner build -->
    <BuildDependsOn>$(QtOuterBuildPrepare);QtOuterBuild;$(QtOuterBuildFinalize)</BuildDependsOn>
    <QtInnerBuild>$(MSBuildProjectFullPath)</QtInnerBuild>
    <RandomFileName>$([System.IO.Path]::GetRandomFileName())</RandomFileName>
  </PropertyGroup>

  <PropertyGroup
    Condition="'$(QtInnerBuild)' != '$(MSBuildProjectFullPath)' AND '$(DesignTimeBuild)' != 'true'">
    <!--// Dependent project inner build: skip build -->
    <BuildDependsOn>$(QtOuterBuildPrepare);$(QtOuterBuildFinalize)</BuildDependsOn>
  </PropertyGroup>

</Project>
