<?xml version="1.0" encoding="utf-8"?>
<!--
/***************************************************************************************************
 Copyright (C) 2023 The Qt Company Ltd.
 SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
***************************************************************************************************/
-->

<!--
///////////////////////////////////////////////////////////////////////////////////////////////////
// Generate imports of QML static plugins
// -->
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <!--
  /////////////////////////////////////////////////////////////////////////////////////////////////
  // Import pre-requisites
  // -->
  <Import
    Condition="'$(QtMsBuildTargets_BeforeQmlStatic)' != ''"
    Project="$(QtMsBuildTargets_BeforeQmlStatic)"/>

  <!--
  /////////////////////////////////////////////////////////////////////////////////////////////////
  // Qt/MSBuild global properties
  //  * add QtQmlStatic to the list of targets to run during build
  //  * add QtQmlStaticLink to the list of targets to run when calculating linker input
  // -->
  <PropertyGroup>
    <QtBuildTargets>$(QtBuildTargets);QtQmlStatic</QtBuildTargets>
    <ComputeLinkInputsTargets>$(ComputeLinkInputsTargets);QtQmlStaticLink</ComputeLinkInputsTargets>
  </PropertyGroup>

  <!--
  /////////////////////////////////////////////////////////////////////////////////////////////////
  /// TARGET QtQmlStaticLink
  ///  => runs only once
  ///  => always runs if building against a static build of Qt
  ///  => targets that must run first: QtQmlStatic, ComputeCLGeneratedLinkInputs
  /////////////////////////////////////////////////////////////////////////////////////////////////
  // Add QML static plugin libs to Link input.
  // -->
  <Target
    Name="QtQmlStaticLink"
    Condition="'$(QtStaticPlugins)' == 'true' AND '@(QtQml)' != ''"
    DependsOnTargets="QtQmlStatic;ComputeCLGeneratedLinkInputs">
    <ItemGroup>
      <Link>
        <AdditionalDependencies Condition="'@(QtQmlImportLib)' != ''"
          >%(Link.AdditionalDependencies);@(QtQmlImportLib)</AdditionalDependencies>
      </Link>
    </ItemGroup>
  </Target>

  <!--
  /////////////////////////////////////////////////////////////////////////////////////////////////
  /// TARGET QtQmlStatic
  ///  => runs only once
  ///  => always runs if building against a static build of Qt
  ///  => targets that must run first: QtQmlImportsParseJson, QtQmlGetImportCpp
  /////////////////////////////////////////////////////////////////////////////////////////////////
  // Entry point for generating imports of QML static plugins.
  // -->
  <Target
    Name="QtQmlStatic"
    Condition="'$(QtStaticPlugins)' == 'true' AND '@(QtQml)' != ''"
    DependsOnTargets="QtQmlImportsParseJson;QtQmlGetImportCpp">
    <ItemGroup>
      <!-- for each required import, create a reference to the corresponding import lib -->
      <QtQmlImportLib
        Condition="'$(QtBuildConfig)' == 'debug'"
        Include="@(QtQmlImport->'%(Path)\%(Plugin)d.lib')"/>
      <QtQmlImportLib
        Condition="'$(QtBuildConfig)' != 'debug'"
        Include="@(QtQmlImport->'%(Path)\%(Plugin).lib')"/>
    </ItemGroup>
  </Target>

  <!--
  /////////////////////////////////////////////////////////////////////////////////////////////////
  /// TARGET QtQmlGetImportCpp
  ///  => runs only once
  ///  => skipped if generated C++ is up-to-date, relative to all JSON output files
  ///  => targets that must run first: QtQmlImportsParseJson
  /////////////////////////////////////////////////////////////////////////////////////////////////
  // Generate C++ source file with imports of QML static plugins.
  // -->
  <Target
    Name="QtQmlGetImportCpp"
    Condition="'$(QtStaticPlugins)' == 'true' AND '@(QtQml)' != ''"
    DependsOnTargets="QtQmlImportsParseJson"
    Inputs="@(QtQml->'%(ImportJson)')"
    Outputs="$(QtQmlPluginImportCpp)">

    <!-- file header -->
    <PropertyGroup>
      <QmlPluginImportCppHeader>
        <![CDATA[
// This file is autogenerated by Qt/MSBuild. It imports static plugin classes for
// static plugins used by QML imports.
#include <QtPlugin>]]>
      </QmlPluginImportCppHeader>
    </PropertyGroup>

    <!-- file contents: header + imports -->
    <ItemGroup>
      <QmlPluginImportCpp Include="$(QmlPluginImportCppHeader)" />
      <QmlPluginImportCpp Include="@(QtQmlImport->'Q_IMPORT_PLUGIN(%(ClassName))')" />
    </ItemGroup>

    <!-- write .cpp file -->
    <WriteLinesToFile
      File="$(QtQmlPluginImportCpp)"
      Lines="@(QmlPluginImportCpp)" />

    <!-- clean-up -->
    <PropertyGroup>
      <QmlPluginImportCppHeader/>
    </PropertyGroup>
    <ItemGroup>
      <QtQmlPluginImportCpp Remove="@(QtQmlPluginImportCpp)" />
    </ItemGroup>
  </Target>

  <!--
  /////////////////////////////////////////////////////////////////////////////////////////////////
  /// TARGET QtQmlImportsParseJson
  ///  => runs only once
  ///  => always runs if building agains a static build of Qt
  ///  => targets that must run first: QtQmlScanImports
  /////////////////////////////////////////////////////////////////////////////////////////////////
  // Parse JSON output of qmlimportscanner.
  //  * ATTN: "Naïve" parser: assumes JSON is formatted with each value in a single line
  // -->
  <Target
    Name="QtQmlImportsParseJson"
    Condition="'$(QtStaticPlugins)' == 'true' AND '@(QtQml)' != ''"
    DependsOnTargets="QtQmlScanImports">

    <!-- constants -->
    <PropertyGroup>
      <StrClassName>&quot;classname&quot;: </StrClassName>
      <StrPath>&quot;path&quot;: </StrPath>
      <StrPlugin>&quot;plugin&quot;: </StrPlugin>
    </PropertyGroup>

    <!-- load text of all JSON output files -->
    <ReadLinesFromFile File="%(QtQml.ImportJson)">
      <Output TaskParameter="Lines" ItemName="JsonText"/>
    </ReadLinesFromFile>

    <ItemGroup>
      <!-- list of all lines starting with '"classname": ' -->
      <JsonClassName
        Include="@(JsonText)"
        Condition="$([System.String]::Copy('%(JsonText.Identity)').StartsWith('$(StrClassName)'))" />
      <!-- extract value -->
      <JsonClassName>
        <ClassName>$([System.String]::Copy('%(JsonClassName.Identity)').Substring($(StrClassName.Length)).Trim(',').Trim('"'))</ClassName>
      </JsonClassName>

      <!-- list of all lines starting with '"path": ' -->
      <JsonPath
        Include="@(JsonText)"
        Condition="$([System.String]::Copy('%(JsonText.Identity)').StartsWith('$(StrPath)'))" />
      <!-- extract value -->
      <JsonPath>
        <Path>$([System.String]::Copy('%(JsonPath.Identity)').Substring($(StrPath.Length)).Trim(',').Trim('"'))</Path>
      </JsonPath>

      <!-- list of all lines starting with '"plugin": ' -->
      <JsonPlugin
        Include="@(JsonText)"
        Condition="$([System.String]::Copy('%(JsonText.Identity)').StartsWith('$(StrPlugin)'))" />
      <!-- extract value -->
      <JsonPlugin>
        <Plugin>$([System.String]::Copy('%(JsonPlugin.Identity)').Substring($(StrPlugin.Length)).Trim(',').Trim('"'))</Plugin>
      </JsonPlugin>
    </ItemGroup>

    <!-- join lists of extracted values into single list of imports -->
    <Join LeftItems="@(JsonClassName)" RightItems="@(JsonPath)" On="ROW_NUMBER">
      <Output TaskParameter="Result" ItemName="JsonAux"/>
    </Join>
    <Join LeftItems="@(JsonAux)" RightItems="@(JsonPlugin)" On="ROW_NUMBER">
      <Output TaskParameter="Result" ItemName="Json"/>
    </Join>
    <!-- remove duplicate import items -->
    <RemoveDuplicates Inputs="@(Json->'%(ClassName)')">
      <Output TaskParameter="Filtered" ItemName="QtQmlImport"/>
    </RemoveDuplicates>

    <!-- @(QtQmlImport) => list of required imports -->

    <!-- clean-up -->
    <PropertyGroup>
      <StrClassName/>
      <StrPath/>
      <StrPlugin/>
    </PropertyGroup>
    <ItemGroup>
      <JsonText Remove="@(JsonText)" />
      <JsonClassName Remove="@(JsonClassName)"/>
      <JsonPath Remove="@(JsonPath)"/>
      <JsonPlugin Remove="@(JsonPlugin)"/>
      <JsonAux Remove="@(JsonAux)" />
      <Json Remove="@(Json)" />
    </ItemGroup>
  </Target>

  <!--
  /////////////////////////////////////////////////////////////////////////////////////////////////
  /// TARGET QtQmlScanImports
  ///  => runs once for each QML file
  ///  => skipped if corresponding JSON output file is up-to-date
  ///  => targets that must run first: QtQmlStaticPrepare
  /////////////////////////////////////////////////////////////////////////////////////////////////
  // Run qmlimportscanner tool.
  // -->
  <Target
    Name="QtQmlScanImports"
    Condition="'$(QtStaticPlugins)' == 'true' AND '@(QtQml)' != ''"
    DependsOnTargets="QtQmlStaticPrepare"
    Inputs="%(QtQml.Identity)"
    Outputs="%(QtQml.ImportJson)">

    <!-- create QML work dir -->
    <MakeDir Directories="$(QtQmlIntDir)" />

    <!-- map local paths to build host paths -->
    <ItemGroup>
      <LocalPath Include="QtQmlFile">
        <Name>Path</Name>
        <Item>%(QtQml.Identity)</Item>
        <Value>%(QtQml.Identity)</Value>
      </LocalPath>
      <LocalPath Include="QtQmlImportJson">
        <Name>Path</Name>
        <Item>%(QtQml.ImportJson)</Item>
        <Value>%(QtQml.ImportJson)</Value>
      </LocalPath>
      <LocalPath Include="QtInstallQml">
        <Name>Path</Name>
        <Item>$(QMake_QT_INSTALL_QML_)</Item>
        <Value>$(QMake_QT_INSTALL_QML_)</Value>
      </LocalPath>
    </ItemGroup>
    <HostTranslatePaths Items="@(LocalPath)" Names="Path">
      <Output TaskParameter="Result" ItemName="HostPath"/>
    </HostTranslatePaths>
    <ItemGroup>
      <QtQmlFile Include="@(HostPath->WithMetadataValue('Identity', 'QtQmlFile'))">
        <LocalPath>%(Item)</LocalPath>
        <HostPath>%(Value)</HostPath>
      </QtQmlFile>
      <QtQmlImportJson Include="@(HostPath->WithMetadataValue('Identity', 'QtQmlImportJson'))">
        <LocalPath>%(Item)</LocalPath>
        <HostPath>%(Value)</HostPath>
      </QtQmlImportJson>
      <QtInstallQml Include="@(HostPath->WithMetadataValue('Identity', 'QtInstallQml'))">
        <LocalPath>%(Item)</LocalPath>
        <HostPath>%(Value)</HostPath>
      </QtInstallQml>
    </ItemGroup>

    <!-- qmlimportscanner command line -->
    <PropertyGroup>
      <Cmd><![CDATA["$(QtToolsPath)/qmlimportscanner.exe"]]></Cmd>
      <Cmd><![CDATA[$(Cmd) -qmlFiles @(QtQmlFile->'%(HostPath)')]]></Cmd>
      <Cmd><![CDATA[$(Cmd) -importPath @(QtInstallQml->'%(HostPath)')]]></Cmd>
    </PropertyGroup>

    <!-- run qmlimportscanner -->
    <HostExec
      Command="$(Cmd)" RedirectStdOut="@(QtQmlImportJson->'%(HostPath)')"
      Inputs="@(QtQmlFile->'%(HostPath)')"
      Outputs="@(QtQmlImportJson->'%(HostPath)')"
      RemoteTarget="$(ResolvedRemoteTarget)"
      RemoteProjectDir="$(_ResolvedRemoteProjectDir)" />
  </Target>

  <!--
  /////////////////////////////////////////////////////////////////////////////////////////////////
  /// TARGET QtQmlStaticPrepare
  ///  => runs once for each QML file
  ///  => skipped if $(QtStaticPlugins) is false
  ///  => skipped if there are no QML files in the project
  ///  => targets that must run first: QtQmlCreateItems
  /////////////////////////////////////////////////////////////////////////////////////////////////
  // Calculate path of .json file with the output of qmlimportscanner for each QML file.
  //  * Format: <QML work dir> '\' SHA1(<path to QML file>) '_' <QML file name> '.json'
  // -->
  <Target
    Name="QtQmlStaticPrepare"
    Condition="'$(QtStaticPlugins)' == 'true' AND '@(QtQml)' != ''"
    DependsOnTargets="QtQmlCreateItems"
    Inputs="%(QtQml.Identity)"
    Outputs="@(QtQml->'####### Do not skip this target #######')">
    <!-- calculate SHA1 of QML file path, relative to project dir -->
    <Hash ItemsToHash="@(QtQml)">
      <Output TaskParameter="HashResult" PropertyName="QtQmlIdentityHash"/>
    </Hash>
    <!-- set path to corresponding JSON file -->
    <ItemGroup>
      <QtQml>
        <ImportJson>$([MSBuild]::MakeRelative(
          '$(ProjectDir)','$(QtQmlIntDir)\$(QtQmlIdentityHash)_%(Filename).json'))</ImportJson>
      </QtQml>
    </ItemGroup>
  </Target>

  <!--
  /////////////////////////////////////////////////////////////////////////////////////////////////
  // Import dependants
  // -->
  <Import
    Condition="'$(QtMsBuildTargets_AfterQmlStatic)' != ''"
    Project="$(QtMsBuildTargets_AfterQmlStatic)"/>

</Project>
