Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: CI

on:
push:
branches: [ master, main, dev ]
pull_request:

jobs:
build-dotnet8:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- name: Setup .NET SDK
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.0.x

- name: Build modern SDK-style projects (v2)
run: |
dotnet build src/FlatFile.Core.Modern/FlatFile.Core.Modern.csproj -c Release
dotnet build src/FlatFile.Core.Attributes.Modern/FlatFile.Core.Attributes.Modern.csproj -c Release
dotnet build src/FlatFile.Delimited.Modern/FlatFile.Delimited.Modern.csproj -c Release
dotnet build src/FlatFile.FixedLength.Modern/FlatFile.FixedLength.Modern.csproj -c Release
dotnet build src/FlatFile.Delimited.Attributes.Modern/FlatFile.Delimited.Attributes.Modern.csproj -c Release
dotnet build src/FlatFile.FixedLength.Attributes.Modern/FlatFile.FixedLength.Attributes.Modern.csproj -c Release
dotnet build tests/FlatFile.Modern.Tests/FlatFile.Modern.Tests.csproj -c Release

- name: Run modern test suite
run: dotnet test tests/FlatFile.Modern.Tests/FlatFile.Modern.Tests.csproj -c Release --no-build
48 changes: 48 additions & 0 deletions .github/workflows/publish-nuget.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
name: Publish NuGet (v2)

on:
push:
branches: [ master ]
workflow_dispatch:

permissions:
contents: read

jobs:
publish:
runs-on: ubuntu-latest
env:
NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }}
PACKAGE_VERSION: 2.0.${{ github.run_number }}

steps:
- uses: actions/checkout@v4

- name: Setup .NET SDK
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.0.x

- name: Validate NuGet API key is configured
run: |
if [ -z "$NUGET_API_KEY" ]; then
echo "NUGET_API_KEY secret is not configured." >&2
exit 1
fi

- name: Pack v2 packages
run: |
mkdir -p artifacts/packages
dotnet pack src/FlatFile.Core.Modern/FlatFile.Core.Modern.csproj -c Release -o artifacts/packages /p:Version=$PACKAGE_VERSION
dotnet pack src/FlatFile.Core.Attributes.Modern/FlatFile.Core.Attributes.Modern.csproj -c Release -o artifacts/packages /p:Version=$PACKAGE_VERSION
dotnet pack src/FlatFile.Delimited.Modern/FlatFile.Delimited.Modern.csproj -c Release -o artifacts/packages /p:Version=$PACKAGE_VERSION
dotnet pack src/FlatFile.FixedLength.Modern/FlatFile.FixedLength.Modern.csproj -c Release -o artifacts/packages /p:Version=$PACKAGE_VERSION
dotnet pack src/FlatFile.Delimited.Attributes.Modern/FlatFile.Delimited.Attributes.Modern.csproj -c Release -o artifacts/packages /p:Version=$PACKAGE_VERSION
dotnet pack src/FlatFile.FixedLength.Attributes.Modern/FlatFile.FixedLength.Attributes.Modern.csproj -c Release -o artifacts/packages /p:Version=$PACKAGE_VERSION

- name: Publish packages to NuGet
run: |
dotnet nuget push "artifacts/packages/*.nupkg" \
--api-key "$NUGET_API_KEY" \
--source https://api.nuget.org/v3/index.json \
--skip-duplicate
38 changes: 38 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,44 @@ FlatFile

FlatFile is a library to work with flat files (work up-to 100 times faster then [FileHelpers](https://www.nuget.org/packages/FileHelpers/2.0.0))


## Modernization status

- 🚨 **v2 breaking change**: dropped legacy .NET Framework targets (`net35`-`net48`) and old build pipeline.
- ✅ Modernized runtime support to **.NET 8** only via SDK-style projects.
- ✅ CI now builds modern projects with `dotnet build` on GitHub Actions.

### Modern .NET support

Active projects:

- `src/FlatFile.Core.Modern`
- `src/FlatFile.Core.Attributes.Modern`
- `src/FlatFile.Delimited.Modern`
- `src/FlatFile.FixedLength.Modern`
- `src/FlatFile.Delimited.Attributes.Modern`
- `src/FlatFile.FixedLength.Attributes.Modern`

All of them target `net8.0` and carry package/assembly version `2.0.0`.

### Runtime enhancements (v2)

Recent runtime-focused updates for modern .NET:

- Faster reflection activation path via compiled constructor delegates and concurrent caches.
- Improved string-to-type conversion using cached `TypeConverter` instances and invariant-culture conversion semantics.
- Reduced allocations in parsing paths (for example, char-based trim overloads and span-based quote detection in delimited parser).

### NuGet publishing from GitHub

When changes are merged to `master`, GitHub Actions can publish v2 packages automatically using `.github/workflows/publish-nuget.yml`.

Required repository secret:

- `NUGET_API_KEY`: NuGet.org API key with push permission for FlatFile packages.

The publish workflow packs all `*.Modern` projects and pushes resulting `.nupkg` files to NuGet (`--skip-duplicate`).

### Installing FlatFile

#### Installing all packages
Expand Down
42 changes: 21 additions & 21 deletions assets/psake-common.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -20,43 +20,43 @@ Properties {
### Project information
$solution_path = "$src_dir\$solution"
$sharedAssemblyInfo = "$src_dir\SharedAssemblyInfo.cs"
$config = "Release"
$frameworks = @("NET35", "NET40", "NET45")
$config = "Release"
$modern_projects = @(
"$src_dir\FlatFile.Core.Modern\FlatFile.Core.Modern.csproj",
"$src_dir\FlatFile.Core.Attributes.Modern\FlatFile.Core.Attributes.Modern.csproj",
"$src_dir\FlatFile.Delimited.Modern\FlatFile.Delimited.Modern.csproj",
"$src_dir\FlatFile.FixedLength.Modern\FlatFile.FixedLength.Modern.csproj",
"$src_dir\FlatFile.Delimited.Attributes.Modern\FlatFile.Delimited.Attributes.Modern.csproj",
"$src_dir\FlatFile.FixedLength.Attributes.Modern\FlatFile.FixedLength.Attributes.Modern.csproj"
)

### Files
$releaseNotes = "$base_dir\ChangeLog.md"
}

## Tasks

Task Restore -Description "Restore NuGet packages for solution." {
"Restoring NuGet packages for '$solution_path'..."
Exec { .$nuget restore $solution_path }
Task Restore -Description "Restore .NET packages for modern projects." {
foreach ($project in $modern_projects) {
"Restoring '$project'..."
Exec { dotnet restore $project }
}
}

Task Clean -Description "Clean up build and project folders." {
Clean-Directory $build_dir

if ($solution) {
"Cleaning up '$solution'..."

foreach ($framework in $frameworks) {
Exec { msbuild $solution_path /target:Clean /nologo /verbosity:minimal /p:Framework=$framework}
}
foreach ($project in $modern_projects) {
"Cleaning '$project'..."
Exec { dotnet clean $project -c $config }
}
}

Task Compile -Depends Clean, Restore -Description "Compile all the projects in a solution." {
"Compiling '$solution'..."

$extra = $null
if ($appVeyor) {
$extra = "/logger:C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll"
Task Compile -Depends Clean, Restore -Description "Compile all modern SDK-style projects." {
foreach ($project in $modern_projects) {
"Compiling '$project'..."
Exec { dotnet build $project -c $config --no-restore }
}

foreach ($framework in $frameworks) {
Exec { msbuild $solution_path /p:"Configuration=$config;Framework=$framework" /nologo /verbosity:minimal $extra }
}
}

### Pack functions
Expand Down
48 changes: 48 additions & 0 deletions docs/modernization-plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# FlatFile modernization plan

## Current direction (v2)

This repository now follows a **modern-only** strategy:

1. Legacy .NET Framework build matrix was removed from CI.
2. SDK-style projects under `*.Modern` are the active build path.
3. Active target is `net8.0` with version `2.0.0` (breaking major release).

## Why

The previous mixed strategy (legacy + modern) produced unstable CI and unnecessary maintenance overhead.
A major-version reset enables simpler tooling, faster builds, and a clear support policy.

## Next steps

- Publish v2 packages from modern projects (`dotnet pack`).
- Add analyzers and nullable annotations incrementally.
- Add dedicated test projects targeting `net8.0`.


## CI/CD publishing

- `.github/workflows/publish-nuget.yml` publishes NuGet packages on pushes to `master`.
- Configure repository secret `NUGET_API_KEY` before enabling release merges.
- Package versions are generated as `2.0.<run_number>` in CI.


## Implemented performance updates

- Replaced reflection activation lock+`DynamicInvoke` path with concurrent cached compiled factories.
- Updated conversion pipeline to use converter caching and invariant-culture conversion semantics.
- Applied small parser allocation improvements (`TrimStart/TrimEnd(char)`, span-based quote prefix checks).


## Modern tests

- Added `tests/FlatFile.Modern.Tests` (xUnit, net8.0).
- CI now runs `dotnet test` for the modern test suite.


## Span/Memory guidelines used

- Use `ReadOnlySpan<char>` for scanning/tokenization (delimiter/quote detection) where data remains in-memory and does not need ownership transfer.
- Use `Memory<T>` only when data must survive async boundaries; prefer `Span<T>`/`ReadOnlySpan<T>` in synchronous hot paths.
- Avoid premature `Substring`/`string.Format` allocations in line build/parse loops.
- Keep API compatibility: introduce span optimizations internally first, then expose span APIs in a dedicated v2+ surface when needed.
17 changes: 17 additions & 0 deletions src/Directory.Build.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<Project>
<PropertyGroup Condition="$([System.String]::Copy('$(MSBuildProjectName)').Contains('.Modern'))">
<IsPackable>true</IsPackable>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<Authors>forcewake</Authors>
<Company>Pavel Nasovich</Company>
<Description>FlatFile library for high-performance fixed-length and delimited file processing.</Description>
<PackageProjectUrl>https://github.com/forcewake/FlatFile</PackageProjectUrl>
<RepositoryUrl>https://github.com/forcewake/FlatFile</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageReadmeFile>README.md</PackageReadmeFile>
</PropertyGroup>

<ItemGroup Condition="$([System.String]::Copy('$(MSBuildProjectName)').Contains('.Modern'))">
<None Include="$(MSBuildThisFileDirectory)..\README.md" Pack="true" PackagePath="\" Link="README.md" />
</ItemGroup>
</Project>
57 changes: 57 additions & 0 deletions src/FlatFile.Benchmark/FlatFile.Benchmark.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,63 @@
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>


<PropertyGroup Condition=" '$(Framework)' == 'NET451' ">
<TargetFrameworkVersion>v4.5.1</TargetFrameworkVersion>
<PlatformTarget>AnyCPU</PlatformTarget>
</PropertyGroup>
<PropertyGroup Condition=" '$(Framework)' == 'NET452' ">
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
<PlatformTarget>AnyCPU</PlatformTarget>
</PropertyGroup>
<PropertyGroup Condition=" '$(Framework)' == 'NET46' ">
<TargetFrameworkVersion>v4.6</TargetFrameworkVersion>
<PlatformTarget>AnyCPU</PlatformTarget>
</PropertyGroup>
<PropertyGroup Condition=" '$(Framework)' == 'NET461' ">
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
<PlatformTarget>AnyCPU</PlatformTarget>
</PropertyGroup>
<PropertyGroup Condition=" '$(Framework)' == 'NET462' ">
<TargetFrameworkVersion>v4.6.2</TargetFrameworkVersion>
<PlatformTarget>AnyCPU</PlatformTarget>
</PropertyGroup>
<PropertyGroup Condition=" '$(Framework)' == 'NET47' ">
<TargetFrameworkVersion>v4.7</TargetFrameworkVersion>
<PlatformTarget>AnyCPU</PlatformTarget>
</PropertyGroup>
<PropertyGroup Condition=" '$(Framework)' == 'NET471' ">
<TargetFrameworkVersion>v4.7.1</TargetFrameworkVersion>
<PlatformTarget>AnyCPU</PlatformTarget>
</PropertyGroup>
<PropertyGroup Condition=" '$(Framework)' == 'NET472' ">
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<PlatformTarget>AnyCPU</PlatformTarget>
</PropertyGroup>
<PropertyGroup Condition=" '$(Framework)' == 'NET48' And '$(Configuration)|$(Platform)' == 'Debug|AnyCPU'">
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\$(Configuration)\$(Framework)\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Framework)' == 'NET48' And '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\$(Configuration)\$(Framework)\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<DocumentationFile>bin\$(Configuration)\$(Framework)\FlatFile.Benchmark.XML</DocumentationFile>
</PropertyGroup>

<ItemGroup>
<Reference Include="BenchmarkIt">
<HintPath>..\packages\Benchmark.It.1.2.0\lib\BenchmarkIt.dll</HintPath>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<RootNamespace>FlatFile.Core.Attributes</RootNamespace>
<AssemblyName>FlatFile.Core.Attributes</AssemblyName>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<LangVersion>latest</LangVersion>
<Nullable>disable</Nullable>
<Deterministic>true</Deterministic>
<Version>2.0.0</Version>
<AssemblyVersion>2.0.0.0</AssemblyVersion>
<FileVersion>2.0.0.0</FileVersion>
</PropertyGroup>

<ItemGroup>
<Compile Include="../FlatFile.Core.Attributes/**/*.cs" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="../FlatFile.Core.Modern/FlatFile.Core.Modern.csproj" />
</ItemGroup>
</Project>
Loading